#include "stdafx.h"

//#include <atlbase.h>
#include <oledbdm.h>

#include "DMAlgorithm.h"
#include "DMAttributeSet.h"
#include "DMMiningParameter.h"
#include "DMAlgorithmPersistence.h"
#include "DMAlgorithmTrainPredict.h"
#include "DMAlgorithmNavigation.h"
#include "DMMetadataAndFactory.h"
using namespace PluginAlgorithms;
using namespace System::Globalization;

HRESULT AlgorithmBase::ExportedPrepareFunction(
		IDMContextServices*			in_pContext,
		DM_SUPPORTED_FUNCTION		in_Function,
		ULONG						in_ulFlags,
		ULONG*						in_rgFlags,
		ULONG						in_ulParams,
		DMVariantPtr*				in_rgParams,
		DM_COLUMN_INFO**			out_prgColumnInfo,
		ULONG*						out_pulColumnInfo)
{
	
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	BSTR bstrTmp	=	NULL;

	CComPtr<IDMStringHandler>		spStrHandler;
	CComPtr<IDMVariantPtrHandler>	spVarHandler;
	RETURN_ON_FAILURE( in_pContext->GetStringHandler( &spStrHandler) );
	RETURN_ON_FAILURE( in_pContext->GetVariantHandler(&spVarHandler) );

	CComPtr<IDMMemoryAllocator>	spVectorAlloc;
	CComPtr<IDMMemoryAllocator>	spStringAlloc;
	RETURN_ON_FAILURE( in_pContext->GetVectorAlloc(&spVectorAlloc) );
	RETURN_ON_FAILURE( in_pContext->GetStringAlloc(&spStringAlloc) );


	*out_pulColumnInfo	=	0;
	*out_prgColumnInfo	=	NULL;


	DM_TRY
	{
		m_context->ExecuteForPrepare = true;
		CustomFunctionInfo^ funcInfo	=	AlgorithmMetadataBase::ValidateFunction(in_Function, m_rgCustomFunctionInfo);

		MethodInfo^	mi = funcInfo->Info;
		VARTYPE	vt	=	VT_NULL;
		System::Data::DataTable^ dtable = nullptr;

		if( funcInfo->ReturnType->IsValueType || funcInfo->ReturnType == System::String::typeid)
		{
			// Set the type without invoking the function
			vt	=	AlgorithmMetadataBase::VARTYPEFromSystemType( funcInfo->ReturnType );
		}
		else 
		{
			Object^ obj = InternalInvokeFunction(
								funcInfo,
								0,
								0,
								NULL,
								in_ulFlags,
								in_rgFlags,
								in_ulParams,
								in_rgParams);


			if( !funcInfo->ReturnsTable )
			{
				if( obj == nullptr )
					vt = VT_NULL;
				else
				{
					vt	=	AlgorithmMetadataBase::VARTYPEFromSystemType( obj->GetType() );
				}
			}
			else
			{
				// Function returns table
				if( obj == nullptr )
				{
					throw gcnew System::Exception( "Custom Function " + funcInfo->Name + " should return a table, but it returned NULL" );
				}

				if( !System::Data::DataTable::typeid->IsInstanceOfType( obj) )
					throw gcnew System::Exception( "Custom Function " + funcInfo->Name + " should return a table, but it returned: " +  obj->GetType()->Name);

				dtable = dynamic_cast<System::Data::DataTable^>(obj);
			}
		}

		// Prepare the function colInfo
		if( dtable == nullptr )
		{
			// Scalar-returning function
			//-------------------------------------------------------------------
			// Allocate memory for column info
			//-------------------------------------------------------------------
			THROW_ON_FAILURE( spVectorAlloc->Alloc(sizeof(DM_COLUMN_INFO), (byte**)out_prgColumnInfo ) );
			memset(*out_prgColumnInfo, 0, sizeof(DM_COLUMN_INFO));

			*out_pulColumnInfo = 1;

			THROW_ON_FAILURE( spStrHandler->CreateNewStringHandle(&((*out_prgColumnInfo)[0].pstrColumnName)) );

			bstrTmp = (BSTR)(void*)System::Runtime::InteropServices::Marshal::StringToBSTR( funcInfo->Name );

			THROW_ON_FAILURE( spStrHandler->CopyBufferToHandle((*out_prgColumnInfo)[0].pstrColumnName, bstrTmp, funcInfo->Name->Length ) );

			(*out_prgColumnInfo)[0].vtColumnType = vt;
			(*out_prgColumnInfo)[0].fIsTable = FALSE;
		}
		else
		{
			// Table-returning function
			int nColumns = dtable->Columns->Count;
			//-------------------------------------------------------------------
			// Allocate memory for column info
			//-------------------------------------------------------------------
			THROW_ON_FAILURE( spVectorAlloc->Alloc(sizeof(DM_COLUMN_INFO)*nColumns, (byte**)out_prgColumnInfo ) );
			memset(*out_prgColumnInfo , 0, sizeof(DM_COLUMN_INFO)*nColumns);

			*out_pulColumnInfo = nColumns;

			for (int i = 0; i < nColumns; i++)
			{
				THROW_ON_FAILURE( spStrHandler->CreateNewStringHandle(&((*out_prgColumnInfo)[i].pstrColumnName)) );
				System::Data::DataColumn^ col = dtable->Columns[i];
				bstrTmp = (BSTR)(void*)System::Runtime::InteropServices::Marshal::StringToBSTR( col->ColumnName );
				THROW_ON_FAILURE( spStrHandler->CopyBufferToHandle((*out_prgColumnInfo)[i].pstrColumnName, bstrTmp, col->ColumnName->Length) );

				(*out_prgColumnInfo)[i].vtColumnType = AlgorithmMetadataBase::VARTYPEFromSystemType( col->DataType );
				(*out_prgColumnInfo)[i].fIsTable = FALSE;
			}
		}
	}
	DM_CATCH_EX(System::Exception^ ex)
	{
		DM_HANDLE_EXCEPTION(ex);

		// cleanup
		for( ULONG i = 0 ; i < *out_pulColumnInfo; i ++ )
		{
			if  ((*out_prgColumnInfo)[i].pstrColumnName)
				spStrHandler->DestroyStringHandle((*out_prgColumnInfo)[i].pstrColumnName);
		}
		if( *out_prgColumnInfo != NULL )
		{
			spVectorAlloc->Free( (byte*)*out_prgColumnInfo );
		}
		*out_prgColumnInfo = NULL;
		*out_pulColumnInfo = 0;
		
		if( bstrTmp != NULL )
			System::Runtime::InteropServices::Marshal::FreeBSTR( (IntPtr)(void*)bstrTmp );
	}
	DM_FINALLY
	{
		m_context->ExecuteForPrepare = false;
	}

    REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedInvokeFunction(
		IDMContextServices*			in_pContext,
		DM_SUPPORTED_FUNCTION		in_Function,
		DM_CaseID					in_CaseID,
		ULONG						in_ulCaseValues,
		DM_ATTRIBUTE_VALUE*			in_rgValues,
		ULONG						in_ulFlags,
		ULONG*						in_rgFlags,
		ULONG						in_ulParams,
		DMVariantPtr*				in_rgParams,
		DMVariantPtr				io_pResult)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	VARIANT		varResult;
	::VariantInit(&varResult);


	CComPtr<IDMVariantPtrHandler>	spVarHandler;
	RETURN_ON_FAILURE( in_pContext->GetVariantHandler(&spVarHandler) );


	DM_TRY
	{
		CustomFunctionInfo^ funcInfo	=	AlgorithmMetadataBase::ValidateFunction(in_Function, m_rgCustomFunctionInfo);

		MethodInfo^	mi = funcInfo->Info;
		

		Object^ obj = InternalInvokeFunction(
							funcInfo,
							in_CaseID,
							in_ulCaseValues,
							in_rgValues,
							in_ulFlags,
							in_rgFlags,
							in_ulParams,
							in_rgParams);

		if( !funcInfo->ReturnsTable )
		{
			if( obj == nullptr )
			{
				V_VT(&varResult)	=	VT_NULL;
			}
			else
			{
				System::Runtime::InteropServices::Marshal::GetNativeVariantForObject( obj, (IntPtr)&varResult);
			}
		}
		else
		{
			// Function returns table
			if( obj == nullptr )
			{
				throw gcnew System::Exception( "Custom Function " + funcInfo->Name + " should return a table, but it returned NULL" );
			}

			if( !System::Data::DataTable::typeid->IsInstanceOfType( obj) )
				throw gcnew System::Exception( "Custom Function " + funcInfo->Name + " should return a table, but it returned: " +  obj->GetType()->Name);

			System::Data::DataTable^	dtable = dynamic_cast<System::Data::DataTable^>(obj);

			InternalTableResult^	 tableRet = gcnew InternalTableResult( dtable );
			System::IntPtr wrappedUnk = System::Runtime::InteropServices::Marshal::GetIUnknownForObject( tableRet );
			V_VT(&varResult)		=	VT_UNKNOWN;
			V_UNKNOWN(&varResult)	=	(IUnknown*)(INT_PTR)wrappedUnk;
		}
		THROW_ON_FAILURE( spVarHandler->CopyVariantToHandle(io_pResult, &varResult) );
	}	
	DM_CATCHALL;

	REPORT_STATUS;
}


Object^	AlgorithmBase::	InternalInvokeFunction(
		CustomFunctionInfo^			in_funcInfo,
		DM_CaseID					in_CaseID,
		ULONG						in_ulCaseValues,
		DM_ATTRIBUTE_VALUE*			in_rgValues,
		ULONG						in_ulFlags,
		ULONG*						in_rgFlags,
		ULONG						in_ulParams,
		DMVariantPtr*				in_rgParams)
{

	AutoUseContext	apEC;

	USES_ERROR_HANDLING;
	CComPtr<IDMVariantPtrHandler>	spVarHandler;
	THROW_ON_FAILURE( apEC->GetVariantHandler(&spVarHandler) );

	if(! in_funcInfo->Valid )
	{
		throw gcnew System::Exception( in_funcInfo->Error );
	}

	MethodInfo^	mi = in_funcInfo->Info;

	
	// Prepare the MiningCase
	MiningCase ^	inputCase	=	nullptr;
	if( in_rgValues && in_funcInfo->CurrentCase != nullptr)
	{
		inputCase = gcnew MiningCase();
		DMCaseItr caseiter;
		
		// Initialize the case iterator with the input values
		caseiter.SetCase(in_rgValues, in_ulCaseValues);
		inputCase->SetCaseIterator( &caseiter );
	}


	// Need to invoke the function with flags and get the actual type
	// Populate the parameters of the function for pre-invocation
	array<ParameterInfo^>^	methodParams		= mi->GetParameters();
	


	array<Object^>^			rgInvokeArguments	=	gcnew array<Object^>( methodParams->Length );



	// Prepare the flags vector
	array<bool>	^			arFlagVector = gcnew array<bool>( in_funcInfo->FlagsCount );
	for( int nIndex = 0; nIndex < in_funcInfo->FlagsCount; nIndex ++ )
		arFlagVector[nIndex] = false;
	for( int nIndex = 0; nIndex < (int)in_ulFlags; nIndex ++ )
	{
		ULONG flag = in_rgFlags[nIndex];
		arFlagVector[(int)flag] = true;
	}

	Object^ objResult	=	nullptr; 
	try
	{
		// set the MiningCase parameter
		if( in_funcInfo->CurrentCase != nullptr )
		{
			// No current case
			rgInvokeArguments[in_funcInfo->CurrentCase->ParamIndex] = inputCase;
		}

		// set the flags to true or false
		for (int nIndex = 0; nIndex < in_funcInfo->FlagsCount; nIndex ++)
		{
			CustomFunctionFlagInfo^ flagInfo = in_funcInfo->Flag[nIndex];
			rgInvokeArguments[flagInfo->ParamIndex]  = arFlagVector[nIndex];
		}

		// set the parameter values
		if( in_ulParams != in_funcInfo->ParametersCount )
			throw gcnew System::Exception( "Invalid number of parameters passed to the custom function:" + in_funcInfo->Name );
		
		for (int nIndex = 0; nIndex < in_funcInfo->ParametersCount; nIndex ++)
		{
			CustomFunctionParameterInfo^ paramInfo = in_funcInfo->Parameter[nIndex];
			DMVariantPtr	pVar	=	in_rgParams[nIndex];
			
			if( pVar == NULL )
			{
				rgInvokeArguments[paramInfo->ParamIndex]  = nullptr;
			}
			else
			{
				VARIANT varParam;
				::VariantInit(&varParam);
				rgInvokeArguments[paramInfo->ParamIndex] = nullptr;
				THROW_ON_FAILURE( spVarHandler->CopyHandleToVariant( pVar, &varParam ) );
				if( paramInfo->ParamType  == DMFPT_CONST_EXPRESSION )
				{
					Object^	paramObj = System::Runtime::InteropServices::Marshal::GetObjectForNativeVariant( (IntPtr)&varParam );
					rgInvokeArguments[paramInfo->ParamIndex]  =	System::Convert::ChangeType( paramObj, paramInfo->ManagedType, CultureInfo::InvariantCulture );
				}
				else
				{
					// AttributeGroup
					
					if( V_VT(&varParam) == VT_UNKNOWN && NULL != V_UNKNOWN(&varParam) )

					{
						IUnknown*	pUnk	=	V_UNKNOWN(&varParam);
						CComPtr<IDMAttributeGroup>	spAttrGroup;
						THROW_ON_FAILURE( pUnk->QueryInterface(&spAttrGroup) );

						System::Collections::ArrayList ^	rgArrAtts	=	gcnew System::Collections::ArrayList();

						THROW_ON_FAILURE( spAttrGroup->Reset() );
						DM_Attribute dmAttr = DM_UNSPECIFIED;
						while ((CURRENT_STATUS = spAttrGroup->Next(&dmAttr)) != S_FALSE)
						{
							THROW_ON_FAILURE(CURRENT_STATUS);
							if( dmAttr == DM_UNSPECIFIED )
								continue;
							rgArrAtts->Add( ((System::UInt32)dmAttr));
						}
						::VariantClear(&varParam);
						rgInvokeArguments[paramInfo->ParamIndex] = gcnew AttributeGroup( rgArrAtts );
					}
					
				}
			}
		}
		
		objResult = mi->Invoke( this, rgInvokeArguments );
	}
	finally
	{
		for( int nIndex = 0; nIndex < methodParams->Length; nIndex ++ )
		{
			rgInvokeArguments[nIndex] = nullptr;
		}
		rgInvokeArguments = nullptr;

		inputCase = nullptr;
	}
	
	
	return objResult;
}

HRESULT InternalTableResult::ExportedGetColumnCount(
		ULONG*			out_pulColumnInfo)
{
	USES_ERROR_HANDLING;
	DM_TRY
	{
		*out_pulColumnInfo	=	m_table->Columns->Count;
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}

HRESULT InternalTableResult::ExportedStart()
{
	if( m_nMax > 0 )
	{
		m_nRowIndex = 0;
		return S_OK;
	}
	else
		return S_FALSE;
}

HRESULT InternalTableResult::ExportedMoveToNextRow()
{
	m_nRowIndex ++;
	if( m_nRowIndex < m_nMax )
		return S_OK;
	return S_FALSE;
}


HRESULT InternalTableResult::ExportedGetColumnValue(
		IDMContextServices*			in_pContext,
		ULONG						in_iColumn,
		DMVariantPtr				io_pColumnValue)
{
	AutoPushContext autoCtx(in_pContext);

	USES_ERROR_HANDLING;
	CComPtr<IDMVariantPtrHandler>	spVarHandler;
	RETURN_ON_FAILURE( in_pContext->GetVariantHandler(&spVarHandler) );
	VARIANT varTmp;
	::VariantInit( &varTmp );
	
	DM_TRY
	{
		System::Data::DataRow^ row = m_table->Rows[m_nRowIndex];
		Object^ value = row[in_iColumn];

		if( value == nullptr )
		{
			V_VT(&varTmp ) = VT_NULL;
		}
		else
		{
			System::Runtime::InteropServices::Marshal::GetNativeVariantForObject( value, (IntPtr)&varTmp);
		}

		THROW_ON_FAILURE( spVarHandler->CopyVariantToHandle(io_pColumnValue, &varTmp) );

	}
	DM_CATCHALL;
	REPORT_STATUS;
}

